   %include "boot.inc"
   section loader vstart=LOADER_BASE_ADDR
   LOADER_STACK_TOP equ LOADER_BASE_ADDR

   BaseOfLoader	equ	0x00		   ;要读取文件缓存区的基址
   OffsetOfLoader	equ	0x9000		;要读取文件缓存区的偏移地址

   RootDirSectors	equ	14
   SectorNumOfRootDirStart	equ	19
   SectorNumOfFAT1Start	equ	1
   SectorBalance	equ	17	

   
	BPB_BytesPerSec	equ	512
	BPB_SecPerTrk	equ	18
	BS_DrvNum	equ	0

   org 0x900
   ; jmp loader_start
;构建gdt及其内部的描述符
   GDT_BASE:   dd    0x00000000 
	       dd    0x00000000

   CODE_DESC:  dd    0x0000FFFF 
	       dd    DESC_CODE_HIGH4

   DATA_STACK_DESC:  dd    0x0000FFFF
		     dd    DESC_DATA_HIGH4

   VIDEO_DESC: dd    0x80000007	       ; limit=(0xbffff-0xb8000)/4k=0x7
	       dd    DESC_VIDEO_HIGH4  ; 此时dpl为0

   GDT_SIZE   equ   $ - GDT_BASE
   GDT_LIMIT   equ   GDT_SIZE -	1 
   times 60 dq 0					 ; 此处预留60个描述符的空位(slot)
   SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0         ; 相当于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0
   SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0	 ; 同上
   SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0	 ; 同上 

   ; total_mem_bytes用于保存内存容量,以字节为单位,此位置比较好记。
   ; 当前偏移loader.bin文件头0x200字节,loader.bin的加载地址是0x900,
   ; 故total_mem_bytes内存中的地址是0xb00.将来在内核中咱们会引用此地址
   total_mem_bytes dd 0					 
   ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

   ;以下是定义gdt的指针，前2字节是gdt界限，后4字节是gdt起始地址
   gdt_ptr  dw  GDT_LIMIT 
	    dd  GDT_BASE

   ;人工对齐:total_mem_bytes4字节+gdt_ptr6字节+ards_buf244字节+ards_nr2,共256字节
   ards_buf times 244 db 0
   ards_nr dw 0		      ;用于记录ards结构体数量

   loader_start:
   ;清屏
   mov     ax, 0600h
   mov     bx, 0700h			
   mov     cx, 0				; 左上角: (0, 0)
   mov     dx, 184fh			; 右下角: (80,25),
								; 因为VGA文本模式中，一行只能容纳80个字符,共25行。
								; 下标从0开始，所以0x18=24,0x4f=79
   int     10h					; int 10h
   
;-------  int 15h eax = 0000E820h ,edx = 534D4150h ('SMAP') 获取内存布局  -------

   xor ebx, ebx		      ;第一次调用时，ebx值要为0
   mov edx, 0x534d4150	      ;edx只赋值一次，循环体中不会改变
   mov di, ards_buf	      ;ards结构缓冲区
.e820_mem_get_loop:	      ;循环获取每个ARDS内存范围描述结构
   mov byte [gs:180], '1'
   mov eax, 0x0000e820	      ;执行int 0x15后,eax值变为0x534d4150,所以每次执行int前都要更新为子功能号。
   mov ecx, 20		      ;ARDS地址范围描述符结构大小是20字节
   int 0x15
   jc .e820_failed_so_try_e801   ;若cf位为1则有错误发生，尝试0xe801子功能
   add di, cx		      ;使di增加20字节指向缓冲区中新的ARDS结构位置
   inc word [ards_nr]	      ;记录ARDS数量
   cmp ebx, 0		      ;若ebx为0且cf不为1,这说明ards全部返回，当前已是最后一个
   jnz .e820_mem_get_loop

;在所有ards结构中，找出(base_add_low + length_low)的最大值，即内存的容量。
   mov cx, [ards_nr]	      ;遍历每一个ARDS结构体,循环次数是ARDS的数量
   mov ebx, ards_buf 
   xor edx, edx		      ;edx为最大的内存容量,在此先清0
.find_max_mem_area:	      ;无须判断type是否为1,最大的内存块一定是可被使用
   mov byte [gs:190], '2'
   mov eax, [ebx]	      ;base_add_low
   add eax, [ebx+8]	      ;length_low
   add ebx, 20		      ;指向缓冲区中下一个ARDS结构
   cmp edx, eax		      ;冒泡排序，找出最大,edx寄存器始终是最大的内存容量
   jge .next_ards
   mov edx, eax		      ;edx为总内存大小
.next_ards:
   loop .find_max_mem_area
   jmp .mem_get_ok

;------  int 15h ax = E801h 获取内存大小,最大支持4G  ------
; 返回后, ax cx 值一样,以KB为单位,bx dx值一样,以64KB为单位
; 在ax和cx寄存器中为低16M,在bx和dx寄存器中为16MB到4G。
.e820_failed_so_try_e801:
   mov byte [gs:200], '3'
   mov ax,0xe801
   int 0x15
   jc .e801_failed_so_try88   ;若当前e801方法失败,就尝试0x88方法

;1 先算出低15M的内存,ax和cx中是以KB为单位的内存数量,将其转换为以byte为单位
   mov cx,0x400	     ;cx和ax值一样,cx用做乘数
   mul cx 
   shl edx,16
   and eax,0x0000FFFF
   or edx,eax
   add edx, 0x100000 ;ax只是15MB,故要加1MB
   mov esi,edx       ;先把低15MB的内存容量存入esi寄存器备份

;2 再将16MB以上的内存转换为byte为单位,寄存器bx和dx中是以64KB为单位的内存数量
   xor eax,eax
   mov ax,bx		
   mov ecx, 0x10000	;0x10000十进制为64KB
   mul ecx		      ;32位乘法,默认的被乘数是eax,积为64位,高32位存入edx,低32位存入eax.
   add esi,eax		   ;由于此方法只能测出4G以内的内存,故32位eax足够了,edx肯定为0,只加eax便可
   mov edx,esi		   ;edx为总内存大小
   jmp .mem_get_ok

;-----------------  int 15h ah = 0x88 获取内存大小,只能获取64M之内  ----------
.e801_failed_so_try88: 
   ;int 15后，ax存入的是以kb为单位的内存容量
   mov  ah, 0x88
   int  0x15
   ; jc .error_hlt
   jc $
   and eax,0x0000FFFF
      
   ;16位乘法，被乘数是ax,积为32位.积的高16位在dx中，积的低16位在ax中
   mov cx, 0x400     ;0x400等于1024,将ax中的内存容量换为以byte为单位
   mul cx
   shl edx, 16	      ;把dx移到高16位
   or edx, eax	      ;把积的低16位组合到edx,为32位的积
   add edx,0x100000  ;0x88子功能只会返回1MB以上的内存,故实际内存大小要加上1MB

.mem_get_ok:
   mov [total_mem_bytes], edx	 ;将内存换为byte单位后存入total_mem_bytes处。


;===========================	search kernel.bin===========================================
;
;=========================================================================================

	mov	word	[SectorNo],	SectorNumOfRootDirStart ;根目录从第19扇区开始

Lable_Search_In_Root_Dir_Begin:

	cmp	word [RootDirSizeForLoop], 0	;判断根目录是否遍历完
	jz	Label_No_LoaderBin				   ;遍历完仍然没有找到，跳转错误提示
	
	dec	word [RootDirSizeForLoop]		;遍历次数减一
	mov	ax,	00h							
	mov	es,	ax                      ;给es置0
	mov	bx,	8000h						;缓冲区偏移地址
	mov	ax,	[SectorNo]					;当前扇区数
	mov	cl,	1							;cl暂存读取多少个扇区数
	call Func_ReadOneSector				;跳转到磁盘读取函数
			
	mov	si,	LoaderFileName			
	mov	di,	8000h						;读取磁盘后的缓冲区开头地址
	cld									;flag的方向标志位df清零，
										;使变址寄存器si或di的地址指针自动增加，对应相反的是std
			
	mov	dx,	10h							;根目录表每项32B,512B/32B=16=10h

Label_Search_For_LoaderBin:

	cmp	dx,	0
	jz	Label_Goto_Next_Sector_In_Root_Dir;当扇区内16项，比对完后去读取下一个扇区
	dec	dx						   ;每次操作减一，计数
	mov	cx,	11					;文件名字为11B

Label_Cmp_FileName:

	cmp	cx,	0
	jz	Label_FileName_Found	;当11位都比对无误，文件名找到，跳转
	dec	cx						;每次操作减一，计数
	lodsb						;把si指向的存储单元读入al,
								;如果LODSW是读入AX,前面cld设置了自增方向，所以si自加1
	cmp	al,	byte [es:di]
	jz	Label_Go_On				;比较是字母一样则继续
	jmp	Label_Different			;比较不一样时

Label_Go_On:
	
	inc	di						;di+1,对应前面的lodsb的si自增1
	jmp	Label_Cmp_FileName		;继续比对

Label_Different:

	and	di,	0ffe0h				;因为每项目录32B，以32B格式化
	add	di,	20h					;到下一个32B
	mov	si,	LoaderFileName		;重新到达文件名最开头
	jmp	Label_Search_For_LoaderBin		;对下一项目录继续匹配

Label_Goto_Next_Sector_In_Root_Dir:
	
	add	word	[SectorNo],	1			;当前扇区加1
	jmp	Lable_Search_In_Root_Dir_Begin	;读取下一个根目录扇区
	

;=======	display on screen : ERROR:No LOADER Found

Label_No_LoaderBin:

	mov	ax,	1301h				;======================
	mov	bx,	008ch				;
	mov	dx,	0100h				;
	mov	cx,	21					;
	push	ax					;
	mov	ax,	ds					;闪烁显示未找到文件提示
	mov	es,	ax					;
	pop	ax						;
	mov	bp,	NoLoaderMessage		;
	int	10h						;
	jmp	$						;======================


;=======	found loader.bin name in root director struct

Label_FileName_Found:

	mov	ax,	RootDirSectors		;ax=14
	and	di,	0ffe0h				;32B格式化
	add	di,	01ah				;读取目标文件的起始簇号
	mov	cx,	word [es:di]		;把文件起始簇号给cx
	push	cx					;入栈
	add	cx,	ax					;cx=起始簇号+14
	add	cx,	SectorBalance		;cx=起始簇号+14+17
	mov	ax,	BaseOfLoader		;ax=0x00
	mov	es,	ax					;把0x00赋值给es
	mov	bx,	OffsetOfLoader		;把0x9000赋值给bx 
								;把0x9000设置成磁盘读取的缓冲区
	mov	ax,	cx					;把起始簇号+14+17给ax作为磁盘读取的起始地址

Label_Go_On_Loading_File:	
	push	ax					;====================
	push	bx					;
	mov	ah,	0eh					;
	mov	al,	'.'					;每读取一个扇区画一个"."
	mov	bl,	0fh					;
	int	10h						;
	pop	bx						;
	pop	ax						;====================

	mov	cl,	1					;读取的扇区数为1，暂存在cl里
	call Func_ReadOneSector		;读一个扇区函数

	pop	ax						;把文件起始簇号再出栈给ax，
								;对应上面push cx，和下面的push ax下一个簇相对簇号
	call Func_GetFATEntry	

	cmp	ax,	0fffh				;判断是否文件最后一个结束簇标识0fffh
	jz	Label_File_Loaded		;跳转到文件中

	push	ax					;把下一个相对簇号入栈，对应前面pop ax	
	mov	dx,	RootDirSectors		;dx=14
	add	ax,	dx					;ax+14
	add	ax,	SectorBalance		;ax+14+17
	add	bx,	BPB_BytesPerSec	;bx=512
	jmp	Label_Go_On_Loading_File;跳转去加载下一个簇号指示下的下一个扇区

;=======	read one sector from floppy

Func_ReadOneSector:

	;=========================================================
	;lba转chs公式:	 逻辑扇区号=(柱面*磁头数)*每个磁道扇区数+扇区号-1
	;				磁道内扇区号=逻辑扇区号%磁道内扇区数+1
	;				(柱面*磁头数)=磁道数=(逻辑扇区号/磁道内扇区数)
	;=========================================================

	push bp						; 把bp保存下来入栈
	mov	bp,	sp					; 把当前栈给bp
	sub	esp, 2					; 栈顶指向
	mov	byte [bp - 2], cl		; 把cl放入栈顶,cl=1,[bp-2]=1
	push bx						; 把bx缓冲区偏移地址保存入栈 bx=8000h
	mov	bl,	BPB_SecPerTrk		; 每个扇区的磁道数量放入bl
	div	bl						; ax除以bl,保存在ax
	inc	ah						; ah暂存扇区号，起始扇区号为1，所以加1
	mov	cl,	ah					; 把扇区号给cl，int 13h
	mov	dh,	al					; 把(柱面*磁头数)暂存到dh
	shr	al,	1					; al右移一位，al除以两个磁头数
	mov	ch,	al					; 把柱面号给ch，int 13h
	and	dh,	1					; 判断磁头数为几，int 13h
	pop	bx						; 设置缓冲区为es:8000h，int 13h
	mov	dl,	BS_DrvNum			; 把软盘号给dl
Label_Go_On_Reading:
	mov	ah,	2					; ah=2模式 读取磁盘 int 13
	mov	al,	byte [bp - 2]		; [bp-2]=1,al=1,读取一个扇区 int 13h
	int	13h
	jc	Label_Go_On_Reading		; cf=0时读取完毕
	add	esp, 2					; 把栈回到指向bp
	pop	bp						; 恢复bp
	ret

;=======	get FAT Entry

Func_GetFATEntry:

	push	es
	push	bx
	push	ax
	mov	ax,	00
	mov	es,	ax
	pop	ax						;ax=起始簇号(FAT表项号)
	mov	byte [Odd],	0			;初始化odd
	mov	bx,	3					;===========
	mul	bx						;ax乘以3除以2，(每个表象12bit=1.5B)
	mov	bx,	2					;ax=1.5B*ax
	div	bx						;===========
	cmp	dx,	0					;判断是否有余数，有余数说明是从后半个字节开始的
	jz	Label_Even				;
	mov	byte [Odd],	1			;有余数置1

Label_Even:

	xor	dx,	dx					;清零dx
	mov	bx,	BPB_BytesPerSec	;bx=512
	div	bx						;ax=(1.5B*ax)/512。表示表项扇区的第几扇区
	push	dx					;把表项在扇区内的位置入栈
	mov	bx,	8000h				;es:bx的int 13读盘缓冲区设置
	add	ax,	SectorNumOfFAT1Start;ax加上表项扇区起始扇区
	mov	cl,	2					;cl暂存读取的扇区个数
	call	Func_ReadOneSector	;读取扇区
	
	pop	dx						;dx=表项在扇区内的位置
	add	bx,	dx					;bx是缓冲区的开始，加上表项在扇区的位置
	mov	ax,	[es:bx]				;把表项开始的双字节（16bit，2B）给ax
	cmp	byte [Odd],	1			;比较是奇数项还是偶数项
	jnz	Label_Even_2			;当是单数项跳转
	shr	ax,	4					;是偶数项取前12位

Label_Even_2:
	and	ax,	0fffh				;是奇数项时取后12位
	pop	bx
	pop	es
	ret

Label_File_Loaded:
   mov byte [gs:320], 'H'
; .hlt:
;    hlt
;    jmp .hlt
; jmp	BaseOfLoader:OffsetOfLoader + 0x300

;-----------------   准备进入保护模式   -------------------
;1 打开A20
;2 加载gdt
;3 将cr0的pe位置1

   ;-----------------  打开A20  ----------------
   in al,0x92
   or al,0000_0010B
   out 0x92,al
   cli ;关中断
   ;-----------------  加载GDT  ----------------
   lgdt [gdt_ptr]

   ;-----------------  cr0第0位置1  ----------------
   mov eax, cr0
   or eax, 0x00000001
   mov cr0, eax

   ; ;-----------------  cr0第0位置0  ----------------
	; mov	eax,	cr0
	; and	al,	11111110b
	; mov	cr0,	eax

	; sti

   jmp dword SELECTOR_CODE:p_mode_start	     ; 刷新流水线，避免分支预测的影响,这种cpu优化策略，最怕jmp跳转，
					     ; 这将导致之前做的预测失效，从而起到了刷新的作用。
; .error_hlt:		      ;出错则挂起
;    hlt

[bits 32]
p_mode_start:
   mov ax, SELECTOR_DATA
   mov ds, ax
   mov es, ax
   mov ss, ax
   mov esp,LOADER_STACK_TOP
   mov ax, SELECTOR_VIDEO
   mov gs, ax

   ; 创建页目录及页表并初始化页内存位图
   call setup_page

   ;要将描述符表地址及偏移量写入内存gdt_ptr,一会用新地址重新加载
   sgdt [gdt_ptr]	      ; 存储到原来gdt所有的位置

   ;将gdt描述符中视频段描述符中的段基址+0xc0000000
   mov ebx, [gdt_ptr + 2]  
   or dword [ebx + 0x18 + 4], 0xc0000000      ;视频段是第3个段描述符,每个描述符是8字节,故0x18。
					      ;段描述符的高4字节的最高位是段基址的31~24位

   ;将gdt的基址加上0xc0000000使其成为内核所在的高地址
   add dword [gdt_ptr + 2], 0xc0000000

   add esp, 0xc0000000        ; 将栈指针同样映射到内核地址

   ; 把页目录地址赋给cr3
   mov eax, PAGE_DIR_TABLE_POS
   mov cr3, eax

   ; 打开cr0的pg位(第31位)
   mov eax, cr0
   or eax, 0x80000000
   mov cr0, eax

   ;在开启分页后,用gdt新的地址重新加载
   lgdt [gdt_ptr]             ; 重新加载

   mov byte [gs:160], 'V'     ;视频段段基址已经被更新,用字符v表示virtual addr

   ; jmp $
   jmp SELECTOR_CODE:enter_kernel


;===============内核文件部署====================
enter_kernel:    
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
   call kernel_init
   mov esp, 0xc009f000
   jmp KERNEL_ENTRY_POINT                 ; 用地址0x1500访问测试，结果ok


;-----------------   将kernel.bin中的segment拷贝到编译的地址   -----------
kernel_init:
   xor eax, eax
   xor ebx, ebx		;ebx记录程序头表地址
   xor ecx, ecx		;cx记录程序头表中的program header数量
   xor edx, edx		;dx 记录program header尺寸,即e_phentsize

   mov dx, [KERNEL_BIN_BASE_ADDR + 42]	  ; 偏移文件42字节处的属性是e_phentsize,表示program header大小
   mov ebx, [KERNEL_BIN_BASE_ADDR + 28]   ; 偏移文件开始部分28字节的地方是e_phoff,表示第1 个program header在文件中的偏移量
					  ; 其实该值是0x34,不过还是谨慎一点，这里来读取实际值
   add ebx, KERNEL_BIN_BASE_ADDR
   mov cx, [KERNEL_BIN_BASE_ADDR + 44]    ; 偏移文件开始部分44字节的地方是e_phnum,表示有几个program header
.each_segment:
   cmp byte [ebx + 0], PT_NULL		  ; 若p_type等于 PT_NULL,说明此program header未使用。
   je .PTNULL

   ;为函数memcpy压入参数,参数是从右往左依然压入.函数原型类似于 memcpy(dst,src,size)
   push dword [ebx + 16]		  ; program header中偏移16字节的地方是p_filesz,压入函数memcpy的第三个参数:size
   mov eax, [ebx + 4]			  ; 距程序头偏移量为4字节的位置是p_offset
   add eax, KERNEL_BIN_BASE_ADDR	  ; 加上kernel.bin被加载到的物理地址,eax为该段的物理地址
   push eax				  ; 压入函数memcpy的第二个参数:源地址
   push dword [ebx + 8]			  ; 压入函数memcpy的第一个参数:目的地址,偏移程序头8字节的位置是p_vaddr，这就是目的地址
   call mem_cpy				  ; 调用mem_cpy完成段复制
   add esp,12				  ; 清理栈中压入的三个参数
.PTNULL:
   add ebx, edx				  ; edx为program header大小,即e_phentsize,在此ebx指向下一个program header 
   loop .each_segment
   ret


;----------  逐字节拷贝 mem_cpy(dst,src,size) ------------
;输入:栈中三个参数(dst,src,size)
;输出:无
;---------------------------------------------------------
mem_cpy:		      
   cld
   push ebp
   mov ebp, esp
   push ecx		   ; rep指令用到了ecx，但ecx对于外层段的循环还有用，故先入栈备份
   mov edi, [ebp + 8]	   ; dst
   mov esi, [ebp + 12]	   ; src
   mov ecx, [ebp + 16]	   ; size
   rep movsb		   ; 逐字节拷贝

   ;恢复环境
   pop ecx		
   pop ebp
   ret


;-------------   创建页目录及页表   ---------------
setup_page:
;先把页目录占用的空间逐字节清0
   mov ecx, 4096
   mov esi, 0
.clear_page_dir:
   mov byte [PAGE_DIR_TABLE_POS + esi], 0
   inc esi
   loop .clear_page_dir

;开始创建页目录项(PDE)
.create_pde:				     ; 创建Page Directory Entry
   mov eax, PAGE_DIR_TABLE_POS
   add eax, 0x1000 			     ; 此时eax为第一个页表的位置及属性
   mov ebx, eax				     ; 此处为ebx赋值，是为.create_pte做准备，ebx为基址。

;   下面将页目录项0和0xc00都存为第一个页表的地址，
;   一个页表可表示4MB内存,这样0xc03fffff以下的地址和0x003fffff以下的地址都指向相同的页表，
;   这是为将地址映射为内核地址做准备
   or eax, PG_US_U | PG_RW_W | PG_P	     ; 页目录项的属性RW和P位为1,US为1,表示用户属性,所有特权级别都可以访问.
   mov [PAGE_DIR_TABLE_POS + 0x0], eax       ; 第1个目录项,在页目录表中的第1个目录项写入第一个页表的位置(0x101000)及属性(7)
   mov [PAGE_DIR_TABLE_POS + 0xc00], eax     ; 一个页表项占用4字节,0xc00表示第768个页表占用的目录项,0xc00以上的目录项用于内核空间,
					     ; 也就是页表的0xc0000000~0xffffffff共计1G属于内核,0x0~0xbfffffff共计3G属于用户进程.
   sub eax, 0x1000
   mov [PAGE_DIR_TABLE_POS + 4092], eax	     ; 使最后一个目录项指向页目录表自己的地址

   ;------ 解决编译main以后莫名其妙出现的0x8048000地址对应页号可找到 ---
   add eax, 0x100000 ; 先指向二级页表开始的位置(此前eax是设置了权限位的0x100000, base1)
   mov [PAGE_DIR_TABLE_POS + 128], eax ; 128是肯定的
   sub eax, 0x100000 ; 为了保证不改变eax
   mov edx, PG_US_U | PG_RW_W | PG_P
   add edx, 0x6f000  ;把08048000映射到6f000000
   mov dword [PAGE_DIR_TABLE_POS + 0x100000+72*4], edx;

;下面创建页表项(PTE)
   mov ecx, 256				     ; 1M低端内存 / 每页大小4k = 256
   mov esi, 0
   mov edx, PG_US_U | PG_RW_W | PG_P	     ; 属性为7,US=1,RW=1,P=1
.create_pte:				     ; 创建Page Table Entry
   mov [ebx+esi*4],edx			     ; 此时的ebx已经在上面通过eax赋值为0x101000,也就是第一个页表的地址 
   add edx,4096
   inc esi
   loop .create_pte

;创建内核其它页表的PDE
   mov eax, PAGE_DIR_TABLE_POS
   add eax, 0x2000 		     ; 此时eax为第二个页表的位置
   or eax, PG_US_U | PG_RW_W | PG_P  ; 页目录项的属性US,RW和P位都为1
   mov ebx, PAGE_DIR_TABLE_POS
   mov ecx, 254			     ; 范围为第769~1022的所有目录项数量
   mov esi, 769
.create_kernel_pde:
   mov [ebx+esi*4], eax
   inc esi
   add eax, 0x1000
   loop .create_kernel_pde
   ret


;=======	tmp variable

RootDirSizeForLoop	dw	RootDirSectors	;初始值14
SectorNo		dw	0
Odd			db	0

;=======	display messages

NoLoaderMessage:	db	"ERROR:No KERNEL Found"
LoaderFileName:		db	"KERNEL  BIN",0